#pragma once

#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#include "util.hpp"
#include "log.hpp"

namespace ns_index
{
    struct DocInfo
    {
        std::string title;   // 文档标题
        std::string content; // 文档去标签的内容
        std::string url;     // 文档官网url
        uint64_t doc_id;     // 文档ID
    };

    struct InvertedElement
    {
        uint64_t doc_id;  // 文档ID
        std::string word; // 关键字
        int weight;       // 权重
    };

    // 倒排拉链
    typedef std::vector<InvertedElement> InvertedList;

    class Index
    {
    private:
        // 正排索引和倒排索引
        // 正排索引用的是数组，下标是文档ID;
        std::vector<DocInfo> forward_index;
        // 倒排索引是一个关键字和一个InvertedElement对应的关系，采用unordered_map存储
        std::unordered_map<std::string, InvertedList> Inverted_index;
        static std::mutex mtx;
    private:
        Index() {} // 这里要让编译器不用默认的
        Index(const Index&) = delete;
        void operator=(const Index&) = delete;
        static Index* instance; // 单例模式
    public:
        static Index* GetInstance()
        {
            if(instance == nullptr) //双检查
            {
                mtx.lock();
                if(instance == nullptr)
                {
                    instance =  new Index();
                }
                mtx.unlock();
            }
            return instance;
        }   
        ~Index() {}
    public:
        // 通过doc_id找到文档内容
        DocInfo *GetForwardIndex(uint64_t doc_id)
        {
            if (doc_id >= forward_index.size())
            {
                std::cerr << "doc_id out of range" << std::endl;
                return nullptr;
            }
            return &forward_index[doc_id];
        }

        // 根据关键字查找倒排拉链
        InvertedList *GetInvertedList(const std::string &word)
        {
            auto it = Inverted_index.find(word);
            if (it == Inverted_index.end())
            {
                std::cerr << word << "Have no inverted list" << std::endl;
                return nullptr;
            }
            return &(it->second);
        }

        // 根据./data/raw_html/raw.txt中的去标签的内容，构建正排/倒排索引
        bool BuildIndex(const std::string &input)
        {
            std::ifstream in(input, std::ios::binary | std::ios::in);
            if (!in.is_open())
            {
                std::cerr << "open " << input << " error" << std::endl;
                return false;
            }
            std::string line;
            int count = 0;
            while (std::getline(in, line))
            {
                DocInfo *doc = BuildForwardIndex(line);
                if (doc == nullptr)
                {
                    std::cerr << "Build ForwardIndex failed" << std::endl; // for debug
                    // LOG(WARNING, "Build ForwardIndex failed");
                    continue;
                }
                BuildInvertedIndex(*doc);
                count++;
                if(count % 100 == 0)
                {
                //   std::cout << "established index document " << count << std::endl;
                    LOG(NORMAL, "established index document: "+ std::to_string(count));
                }
            }
            return true;
        }

    private:
        // 建立正排
        DocInfo *BuildForwardIndex(const std::string &line)
        {
            
            // 1. 切分line
            std::vector<std::string> result;
            std::string sep = "\3";
            ns_util::StringUtil::SliceString(line, &result, sep);

            if (result.size() < 3)
            {
                std::cerr << "line " << std::endl;
                return nullptr;
            }
            // 2.保存result内容到DocInfo
            DocInfo doc;
            doc.title = result[0];
            doc.content = result[1];
            doc.url = result[2];
            doc.doc_id = forward_index.size(); // 假设此时大小为0，我们直接把当前要插入的索引置为0，就两个值一致了
            // 3.插入到正排索引forward_index的vector中
            forward_index.push_back(std::move(doc)); // 这里doc代表文件内容，可能太大导致拷贝效率很低，所以不让它拷贝，我们用右值引用
            return &forward_index.back();
        }

        // 采用jieba分词建立倒排
        bool BuildInvertedIndex(const DocInfo &doc)
        {

            struct word_cnt
            {
                int title_cnt;
                int content_cnt;
                word_cnt() : title_cnt(0), content_cnt(0) {}
            };
            std::unordered_map<std::string, word_cnt> word_map; // 用来暂存词频

            // HELLO/hello/Hello 应不应该有区别? 
            // 其实不应该。所以我们要忽略大小写，这里统一转小写

            // 对标题分词 + 词频统计
            std::vector<std::string> title_words;
            ns_util::JiebaUtil::CutString(doc.title, &title_words);
            
            for(auto &s : title_words)
            {
                boost::to_lower(s); // 转小写
                word_map[s].title_cnt++; 
            }

            // 对内容分词 + 词频统计
            std::vector<std::string> content_words;
            ns_util::JiebaUtil::CutString(doc.content, &content_words);

            for(auto &s : content_words)
            {
                boost::to_lower(s); // 转小写
                word_map[s].content_cnt++; 
            }

#define TITLE_WEIGHT 50  // 标题中某个词的权重
#define CONTENT_WEIGHT 1 // 内容中某个词的权重

            for(auto &word_pair : word_map)
            {
                InvertedElement item;
                item.word = word_pair.first;
                item.doc_id = doc.doc_id;
                // 根据标题某个词出现的情况构建搜索相关性
                item.weight = TITLE_WEIGHT*word_pair.second.title_cnt + CONTENT_WEIGHT*word_pair.second.content_cnt; 

                // 构建映射
                InvertedList& InvertedList = Inverted_index[word_pair.first]; // 如果map中没有，自动创建k-v结构
                InvertedList.push_back(std::move(item)); // 填写value

            }
            return true;
        }
    };
    Index* Index::instance = nullptr;
    std::mutex Index::mtx;
}  
